 package gsd.gaudi.client.anonymizer;

import gsd.gaudi.client.converter.GaudiListeners2Events;
import gsd.gaudi.common.GaudiException;
import gsd.gaudi.common.GaudiReport;
import gsd.gaudi.common.model.log.GaudiListener;
import gsd.gaudi.common.model.log.GaudiListenerSequence;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;

public class GaudiATraceGenerator {

	private String mainclass;
	private String filename;
	private String structurefile;
	
	private GaudiReport reportToReduce;
	
	private GaudiListenerSequence originalseq;
	private GaudiException originalexception;
	
	private LinkedList<String> generatedfiles;
	private LinkedList<String> convertedfiles;
	
	private HashMap<String,GaudiListenerSequence> sequences;
	
	private LinkedList<GaudiListenerSequence> anonymized;
	
	private int index = 1;
	
	public GaudiATraceGenerator(String structurefile, String string, String mainclass) {
		
		this.mainclass = mainclass;
		this.filename = string;
		this.structurefile = structurefile;
		this.generatedfiles = new LinkedList<String>();
		this.convertedfiles = new LinkedList<String>();
		this.sequences = new  HashMap<String,GaudiListenerSequence>();
		this.anonymized = new LinkedList<GaudiListenerSequence>();
		
		InputStream file;
		try {
			
			file = new FileInputStream(string);
			InputStream buffer = new BufferedInputStream(file);
			ObjectInput input = new ObjectInputStream (buffer);
			
			this.reportToReduce = (GaudiReport) input.readObject();
			this.originalseq = this.reportToReduce.getListenersequence();
			this.originalexception = this.reportToReduce.getException();
		
			this.filename = this.filename.substring(this.filename.lastIndexOf("/")+1, this.filename.lastIndexOf("."));
			
			
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

	public void generate() {
		 
		System.out.println("[GaudiATraceGenerator]->trying to reduce event sequence.");

		LinkedList<GaudiListener> list = this.originalseq.getListenerseq();
		LinkedList<GaudiListener> newlist = new LinkedList<GaudiListener>();
		
		for(int i = list.size() - 1; i >= 0; i--){
			newlist.addFirst(list.removeLast());
			GaudiListenerSequence newseq = new GaudiListenerSequence();
			newseq.setListenerseq(newlist);
			GaudiReport newreport = new GaudiReport(newseq, this.originalexception);
			System.out.println("[GaudiATraceGenerator]-> a " + newseq.getListenerseq().size() + " listener(s) sequence was generated.");
			this.recordReducedExecution(newreport);		
		}		
	}

	public void recordReducedExecution(GaudiReport newreport){
		
		//System.out.println("[GaudiATraceGenerator]-Recording new sequence to file...");
		
		(new File("gaudi")).mkdir();
		(new File("./gaudi/reduced")).mkdir();
		
		String filename = "./gaudi/reduced/" + this.filename + "-" + this.index + ".txt";
		this.generatedfiles.add(filename);
		this.index++;
		
		this.sequences.put(filename, newreport.getListenersequence());
		
		try{
			OutputStream file = new FileOutputStream(filename);
		    OutputStream buffer = new BufferedOutputStream(file);
		    ObjectOutput output = new ObjectOutputStream(buffer);
		    output.writeObject(newreport);
			output.close();
		}catch (Exception e){
			System.err.println("Error: " + e.getMessage());
		}
		
		//System.out.println("[GaudiATraceGenerator]-sequence recorded in file: " + filename);
	}

	public void convertAll(){
		for(String fil : this.generatedfiles){
			GaudiListeners2Events converter = new GaudiListeners2Events(this.structurefile, fil);
			converter.convert();
			converter.recordToFile();
			String filec = "./gaudi/converted/" + fil.substring(fil.lastIndexOf("/")+1, fil.lastIndexOf(".")) + "_converted.txt";
			this.convertedfiles.add(filec);
			
			InputStream file;
			GaudiListenerSequence seq = null;
			try {
				
				file = new FileInputStream(filec);
				InputStream buffer = new BufferedInputStream(file);
				ObjectInput input = new ObjectInputStream (buffer);
				
				GaudiReport report = (GaudiReport) input.readObject();
				seq = report.getListenersequence();
				
			} catch (FileNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			this.sequences.put(filec, seq);
		}
	}
	
	public void testAll(){
		
		boolean worked = false;
		LinkedList<String> todel = new LinkedList<String>();
		
		for(String file : this.convertedfiles){
			
			if(worked){
				todel.add(file);
			}else{

				worked = this.testSequence(file);

				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
				if(!worked){
					todel.add(file);
				}else{
					System.out.println("[GaudiATraceGenerator]-Found a valid reduced event sequence in file: " + file);
				}
			}
			
		}
		
		for(String file : todel){
			(new File(file)).delete();
			System.out.println("[GaudiATraceGenerator]-Deleting event sequence in file: " + file);
		}
		for(String file : todel){
			this.convertedfiles.remove(file);
		}
		for(String file : this.generatedfiles){
			(new File(file)).delete();
			System.out.println("[GaudiATraceGenerator]-Deleting event sequence in file: " + file);
		}
		
	}
	
	public boolean testSequence(String filename){
		
		System.out.println("[GaudiATraceGenerator]-Testing execution in file: " + filename);
		
		try {
			ProcessBuilder pb = new ProcessBuilder("java", "-jar", "GaudiClient.jar", "-testreplay", filename, this.mainclass);
			Process process = pb.start();
			InputStream is = process.getErrorStream();
			InputStreamReader isr = new InputStreamReader(is);
			BufferedReader br = new BufferedReader(isr);
			String line = "";
			
			while(!br.ready())
				Thread.sleep(1000);
			line = br.readLine();    
			process.destroy();
			
			if(line.contains(this.originalexception.getException().toString()))
				return true;
			else
				return false;
			
		} catch (IOException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
		return true;
		
	}
	
	public void anonymizeReducedSequence(){
		
		for(String file : this.convertedfiles){
		
			System.out.println("[GaudiATraceGenerator]-Processing listener sequence in file: " + file);
			
			GaudiListenerSequence seq = this.sequences.get(file);
			System.out.print("[GaudiATraceGenerator]-Original reduced sequence: ");
			this.printState(seq.getListenerseq(), seq.getListenerseq());
			
			this.anonymizeReducedSequence(seq.getListenerseq());
			Collections.sort(this.anonymized);
			
			System.out.println("[GaudiATraceGenerator]-Ordering list");
			for(GaudiListenerSequence gls : this.anonymized){
				this.printState(seq.getListenerseq(), gls.getListenerseq());
			}
			
			this.convertedfiles = new LinkedList<String>();
			this.generatedfiles = new LinkedList<String>();
			
			this.index = 0;
			for(GaudiListenerSequence lseq : this.anonymized){
				GaudiReport newreport = new GaudiReport(lseq, this.originalexception);
				System.out.println("[GaudiATraceGenerator]-> a " + lseq.getListenerseq().size() + " listener(s) sequence was generated.");
				this.recordReducedExecution(newreport);		
			}
			
		}	
	}
	
	public void anonymizeReducedSequence(LinkedList<GaudiListener> seq){
		
		if(seq.size() > 2){
			
			LinkedList<GaudiListener> basic = new LinkedList<GaudiListener>();
			basic.addLast(seq.getFirst());
			basic.addLast(seq.getLast());
			
			this.printState(seq, basic);
			this.anonymized.add(new GaudiListenerSequence(basic));
			
			if(seq.size() > 2){
				scramble(seq, basic, 1);
			}
			
		}
	}
	
	public void scramble(LinkedList<GaudiListener> oseq, LinkedList<GaudiListener> seq, int i){
	
		if(i >= (oseq.size() - 1))
			return;
		
		scramble(oseq, seq, i + 1);
		
		LinkedList<GaudiListener> aux = new LinkedList<GaudiListener>(seq);
		
		aux.removeLast();
		aux.addLast(oseq.get(i));
		aux.addLast(oseq.getLast());
		
		this.printState(oseq, aux);
		this.anonymized.add(new GaudiListenerSequence(aux));
		
		scramble(oseq, aux, i + 1);
		
	}
	
	
	public void printState(LinkedList<GaudiListener> o, LinkedList<GaudiListener> c){
		
		System.out.print("Generating sequence: {");
		for(GaudiListener gl : c){
			System.out.print("L" + o.indexOf(gl));
			if(c.indexOf(gl) != c.size()-1){
				System.out.print("; ");
			}
		}
		System.out.println("}");
	}
	
}
